/*
 *	Copyright (C) 2008 - Mauricio Bieze Stefani
 *	This file is part of the Insano Game Engine.
 *
 *	Insano is free software: you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation, either version 3 of the License, or
 *	(at your option) any later version.
 *
 *	Insano is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with Insano.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "GameServer.h"
#include <iostream>
#include "SqliteCB.h"
#include "MBSTrace.h"

using namespace std;

GameServer::GameServer()
{
	slots.mudaNumSlots(MAX_PLAYERS);
}

GameServer::~GameServer()
{
	TRACE_INFO("Shuting down server");
	sqlite3_close(db);
}

int GameServer::run()
{
	char *zErrMsg = 0;

	if(SQLITE_OK != sqlite3_open_v2("database.db3", &db, SQLITE_OPEN_READWRITE, NULL))
	{
		TRACE_ERROR("opening file 'database.db3': %s", sqlite3_errmsg(db));
		return -1;
	}

	if(SQLITE_ABORT != sqlite3_exec(db, "SELECT TCP_PORT FROM CONFIG", DB_read_config, &config, &zErrMsg))
	{
		TRACE_ERROR("SQLite: %s", zErrMsg);
		sqlite3_free(zErrMsg);
	}

	if(slots.ouvir(config.port)==0)
		TRACE_INFO("Listenning port %d for connections", config.port);
    else
    {
		TRACE_ERROR("opening port %d", config.port);
		return -1;
    }

	if( (device = irr::createDevice(irr::video::EDT_NULL)) == NULL)
	{
		TRACE_ERROR("Creating Irrlicht device");
		return -1;
	}

    for(;;)
    {
    	//quick hack to avoid consuming 100% cpu
    	device->sleep(100);

		slots.select(1);
		for(int s=0;s<slots.pegaNumSlots();s++)
		{
			MBSSlot *slot=slots[s];

			if(slot->pegaEstado() > MBSSlot::LIVRE)
			{
				if(slot->validFD())
				{
					if(slots.isException(s))
						desconectar(s);
					else
					{
						if(slots.isWrite(s))
							if( slot->pegaEstado() < MBSSlot::HANDSHAKE)
								slot->setaEstado(MBSSlot::LOGIN);
						if(slots.isRead(s))
						{
							if(slot->receiveLoop()<=0)
								desconectar(s);
							MBSBuffer *pacote;
							while((pacote=slot->receive())!=NULL)
							{
								processPacket(s,slot,pacote);
								delete pacote;
							}
						}
					}
				}
			}
		}
    }
}

int GameServer::processPacket(int n, MBSSlot *slot, MBSBuffer *pacote)
{
	unsigned char comando;
	MBSSlot::EstadoSlot estado=slot->pegaEstado();
	pacote->readByte(&comando);

	switch(estado)
	{
		case MBSSlot::LIVRE:
			TRACE_ERROR("read from a slot in free state.");
		break;
		case MBSSlot::RESERVADO:
		case MBSSlot::HANDSHAKE:
		case MBSSlot::LOGIN:
		{
			// ============================
			if((COMANDO)comando==LOGIN_REQ)
			// ============================
			{
				// select user and password from database
				char temp[MAX_NAME+1];
				pacote->readString(temp,MAX_NAME);				//read username
				std::string stmt="SELECT * FROM ACCOUNTS WHERE USER_NAME='"+std::string(temp)+"'";
				pacote->readString(temp,MAX_NAME);				//read password
				stmt+=" AND PASSWORD='" + std::string(temp) +"'";

				if(SQLITE_ABORT == sqlite3_exec(db, stmt.c_str(), DB_read_account, &accounts[n], NULL))
				{
					//ok, entering server
					slot->setaEstado(MBSSlot::CONECTADO);

					MBSBuffer *p=new MBSBuffer(sizeof(COMANDO));
					p->writeByte((COMANDO)LOGIN_OK);
					slot->enviar(p);
					delete p;
				}
				else
					desconectar(n);
			}
			// ======================================
			else if((COMANDO)comando==CREATE_ACC_REQ)
			// ======================================
			{
				//check if already exist
				char username[MAX_NAME+1];
				pacote->readString(username,MAX_NAME);			//read username
				std::string stmt="SELECT * FROM ACCOUNTS WHERE USER_NAME='"+std::string(username)+"'";

				if(SQLITE_OK == sqlite3_exec(db, stmt.c_str(), DB_read_account, &accounts[n], NULL))
				{
					//ok, this is an unique username, create the account
					char temp[MAX_NAME+1];
					pacote->readString(temp,MAX_NAME);			//read password
					stmt="INSERT INTO ACCOUNTS VALUES (NULL,'"+std::string(username)+"','"+std::string(temp)+"')";

					if(SQLITE_OK == sqlite3_exec(db, stmt.c_str(), NULL, NULL, NULL))
					{
						TRACE_INFO("created new account %s", username);

						MBSBuffer *p=new MBSBuffer(sizeof(COMANDO));
						p->writeByte((COMANDO)CREATE_ACC_OK);
						slot->enviar(p);
						delete p;
					}
				}
				else
					desconectar(n);	//already exist
			}
			else
				TRACE_ERROR("invalid state(%d) command(%d)", estado, (int)comando);
		}
		break;
		case MBSSlot::CONECTADO:
		{
			switch((COMANDO)comando)
			{
				// ===============
				case CHAT_CLI_SRV:
				// ===============
				{
					char destinatario[MAX_NAME+1];
					pacote->readString(destinatario,MAX_NAME);
					char mensagem[MAX_MSG+1];
					pacote->readString(mensagem,MAX_MSG);
					sendChat(accounts[n].user_name.c_str(),destinatario,mensagem);
				}
				break;

				// ===============
				case CHAR_LST_REQ:
				// ===============
				{
					ProtoCharList dbcl;
					dbcl.numChars=0;

					char stmt[100+1];
					snprintf(stmt, 100, "SELECT * FROM CHARS WHERE ACCOUNT_ID=%d", accounts[n].account_id);

					if(SQLITE_OK == sqlite3_exec(db, stmt, DB_read_char_list, &dbcl, NULL))
					{
						MBSBuffer *p=new MBSBuffer(sizeof(COMANDO)+sizeof(dbcl.numChars));
						p->writeByte((COMANDO)CHAR_LST_REP);
						p->writeByte(dbcl.numChars);
						for(int x=0; x<dbcl.numChars; x++)
						{
							p->writeLong(dbcl.chars[x].id);
							p->writeString(dbcl.chars[x].name);
							p->writeByte(dbcl.chars[x].race);
							p->writeByte(dbcl.chars[x].gender);
						}
						slot->enviar(p);
						delete p;
					}
				}
				break;

				// =================
				case LOGIN_CHAR_REQ:
				// =================
				{
					unsigned long char_id;
					pacote->readLong(&char_id);

					ProtoCharDef Char;

					char stmt[100+1];
					snprintf(stmt, 100, "SELECT * FROM CHARS WHERE ACCOUNT_ID=%d AND CHAR_ID=%ld", accounts[n].account_id, char_id);

					MBSBuffer *p=new MBSBuffer(sizeof(COMANDO));

					if(SQLITE_ABORT == sqlite3_exec(db, stmt, DB_read_char, &Char, NULL))
					{
						p->writeByte((COMANDO)LOGIN_CHAR_OK);

						accounts[n].user_name=std::string(Char.name);
						chat.entrar(accounts[n].user_name);
					}
					else
						p->writeByte((COMANDO)LOGIN_CHAR_DENY);

					slot->enviar(p);
					delete p;
				}
				break;

				// =================
				case CREATE_CHAR_REQ:
				// =================
				{
					//first check if it already exist
					ProtoCharDef newChar;
					pacote->readString(newChar.name, MAX_NAME);
					pacote->readByte(&newChar.race);
					pacote->readByte(&newChar.gender);

					char stmt[100+1];
					snprintf(stmt, 100, "SELECT * FROM CHARS WHERE CHAR_NAME='%s'", newChar.name);

					if(SQLITE_OK == sqlite3_exec(db, stmt, DB_read_char, &newChar, NULL))
					{
						//ok, this is an unique charname, create it
						snprintf(stmt, 100, "INSERT INTO CHARS VALUES (NULL, %d, '%s', %d, %d)", accounts[n].account_id,
											newChar.name, newChar.race, newChar.gender);

						if(SQLITE_OK == sqlite3_exec(db, stmt, NULL, NULL, NULL))
						{
							TRACE_INFO("created new char %s", newChar.name);

							MBSBuffer *p=new MBSBuffer(sizeof(COMANDO));
							p->writeByte((COMANDO)CREATE_CHAR_OK);
							slot->enviar(p);
							delete p;
							break;
						}
						else
							TRACE_ERROR("inserting into CHARS");
					}

					MBSBuffer *p=new MBSBuffer(sizeof(COMANDO));
					p->writeByte((COMANDO)CREATE_CHAR_DENY);
					slot->enviar(p);
					delete p;
				}
				break;
				default:
					TRACE_ERROR("invalid state(%d) command(%d)", estado, (int)comando);
				break;
			}
		}
		break;
	}
	return 0;
}

int GameServer::sendChat(const char *remetente, const char *destinatario, const char *mensagem)
{
	int tam_rem=strlen(remetente)+1;
//	int tam_dest=strlen(destinatario)+1;
	int tam_msg=strlen(mensagem)+1;

	MBSBuffer *p=new MBSBuffer(sizeof(COMANDO)+tam_rem+tam_msg);
	if(p!=NULL)
	{
		p->writeByte((COMANDO)CHAT_SRV_CLI);
		p->writeString(remetente);
		p->writeString(mensagem);

		TRACE_INFO("[CHAT] %s: %s", remetente, mensagem);
//		cout << "destinatario[" << tam_dest << "]=" << destinatario << endl;
//		cout << "p->disponiveis=" << p->disponiveis() << endl;

		set<std::string>::iterator i;
		for(i=chat.users.begin();i!=chat.users.end();i++)
		{
//			cout << "Enviando pra " << *i << endl;
			for(int x=0;x<MAX_PLAYERS;x++)
				if(accounts[x].user_name==*i)
					slots[x]->enviar(p);
		}
		delete p;
	}
	else
		TRACE_ERROR("Null Buffer");
	return 0;
}

void GameServer::desconectar(int n)
{
	if((n<0)||(n>MAX_PLAYERS)) return;
	if(accounts[n].user_name!="")
	{
		chat.sair(accounts[n].user_name);
		accounts[n].user_name="";
	}
	accounts[n].account_id=-1;
	slots.desconectar(n);
}
